/**
 * @namespace Provides drag and drop functionality. Not 100% complete.
 */
Jelo.DD = function() {
    /** @private convenience */
    function pi(n) {
        return parseInt(n, 10);
    }
    
    /** @private constants */
    var _ = {
        minOffset   : 5, // px until drag
        zHigh       : 20000,
        zHigher     : 20001,
        isDragging  : false,
        item        : null,
        mouseX      : null,
        mouseY      : null,
        lastDragged : null,
        dropTargets : []
    };
    
    /** @private whether drag-drop is on or off */
    var active = false;
    
    /** @private */
    var zeroNaN = function(n) {
        return isNaN(n) ? 0 : n;
    };
    
    /** @private event handler */
    var mouseDown = function(t, e) {
        if (!active) {
            return true;
        }
        if (Jelo.Event.isFixed()) {
            e = t;
        }
        t = this;
        while (!t.jeloDragTarget && t !== null) {
            t = t.parentNode || t.parentElement;
        }
        if (t === null) {
            return false;
        }
        
        var d = t.jeloDragTarget;
        
        // store orig state
        var iPos = findPosition(d);
        console.log(iPos);
        d.jeloDragT = Jelo.css(d, "top");
        d.jeloDragL = Jelo.css(d, "left");
        d.jeloDragP = Jelo.css(d, "position");
        d.jeloDragO = Jelo.css(d, "opacity");
        d.jeloDragZ = Jelo.css(d, "z-index");
        
        // get data for new state
        Jelo.css(d, "position", "absolute");
        Jelo.css(d, "top", iPos.y + "px");
        Jelo.css(d, "left", iPos.x + "px");
        d.jeloDragTop = pi(Jelo.css(d, "top"));
        d.jeloDragLeft = pi(Jelo.css(d, "left"));
        
        // return to orig state
        Jelo.css(d, "position", d.jeloDragP);
        Jelo.css(d, "left", d.jeloDragL);
        Jelo.css(d, "top", d.jeloDragT);
        
        Jelo.on(document, "mousemove", mouseMove);
        Jelo.on(document, "mouseup", mouseUp);
        _.item = d;
        _.isDragging = true;
        _.mouseX = e.clientX;
        _.mouseY = e.clientY;
        
        return false;
    };
    
    /** @private event handler */
    var mouseMove = function(t, e) {
        if (!active || !_.isDragging || !_.item) {
            return true;
        }
        if (Jelo.Event.isFixed()) {
            e = t;
        }
        var x = e.clientX - _.mouseX;
        var y = e.clientY - _.mouseY;
        var ax = Math.abs(e.clientX - _.mouseX);
        var ay = Math.abs(e.clientY - _.mouseY);
        if (Math.round((ax + ay) / 2) > _.minOffset) {
            var d = _.item;
            Jelo.css(d, "position", "absolute");
            Jelo.css(d, "opacity", 0.7);
            Jelo.css(d, "z-index", _.zHigher);
            Jelo.css(d, "top", pi(d.jeloDragTop + y) + "px");
            Jelo.css(d, "left", pi(d.jeloDragLeft + x) + "px");
            document.body.appendChild(d);
        }
        return false;
    };
    
    /** @private event handler */
    var mouseUp = function(t, e) {
        if (!active) {
            return true;
        }
        if (Jelo.Event.isFixed()) {
            e = t;
        }
        if (_.item) {
            if (_.lastDragged != _.item) {
                _.zHigh++;
                _.zHigher++;
            }
            _.lastDragged = _.item;
            Jelo.css(_.item, "opacity", _.item.jeloDragO);
            Jelo.css(_.item, "z-index", _.zHigh);
            if (typeof _.item.jeloDragOnMouseUp == "function") {
                _.item.jeloDragOnMouseUp.call(_.item, _.item.jeloDragHandle);
            }
            
            // handle drops
            Jelo.each(_.dropTargets, function() {
                if (typeof this.jeloOnDrop == "function") {
                    var t = findPosition(_.item);
                    var p = findPosition(this);
                    var r = p.x + zeroNaN(pi(Jelo.css(this, "width"))); // right edge
                    var b = p.y + zeroNaN(pi(Jelo.css(this, "height"))); // bottom edge
                    var mx = e.clientX;
                    var my = e.clientY;
                    if (mx > p.x && mx < r && my > p.y && my < b) {
                        var d = _.item;
                        Jelo.css(d, "position", "static");
                        Jelo.css(d, "left", "auto");
                        Jelo.css(d, "top", "auto");
                        this.appendChild(_.item);
                        this.jeloOnDrop.call(this, _.item,
                            _.item.jeloDragHandle);
                    }
                }
            });
            
        }
        Jelo.un(document, "mousemove", mouseMove);
        Jelo.un(document, "mouseup", mouseUp);
        _.mouseX = null;
        _.mouseY = null;
        _.item = null;
        _.isDragging = false;
    };
    
    var detectDrop = function(t, e) {
        if (Jelo.Event.isFixed()) {
            e = t;
        }
        console.log('test');
    };
    
    /** @private utility */
    var findPosition = function(o) {
        var offsetX = zeroNaN(pi(Jelo.css(o, "margin-left")));
        var offsetY = zeroNaN(pi(Jelo.css(o, "margin-top")));
        var curleft = 0, curtop = 0;
        if (o.offsetParent) {
            curleft = o.offsetLeft;
            curtop = o.offsetTop;
            while ((o = o.offsetParent)) {
                curleft += o.offsetLeft;
                curtop += o.offsetTop;
            }
        }
        return {
            "x" : curleft - offsetX,
            "y" : curtop - offsetY
        };
    };
    
    /** @scope Jelo.DD */
    return {
        /**
         * @param {HTMLElement} element The item to investigate.
         * @returns {Boolean} True if element will respond to drag events.
         */
        isDraggy       : function(el) {
            return (el && !!el.jeloDraggy);
        },
        /**
         * @param {HTMLElement} element The item to investigate.
         * @returns {Boolean} True if element will respond to drop events.
         */
        isDroppy       : function(el) {
            return (el && !!el.jeloDroppy);
        },
        /**
         * Starts or stops listening for drag events on a given element.
         *
         * @param {HTMLElement} element The item to affect.
         * @param {Boolean} [bool=true] True to start listening for drag events, false
         * to stop.
         * @param {HTMLElement} [handle=element] The area that, when dragged,
         * will move the element. Defaults to the whole element itself.
         * @param {Function} [fn] Method to invoke when the element is dropped.
         * The execution context ("this") will be the element itself, and the
         * argument passed will be the handle object.
         */
        setDraggy      : function(el, bool, handle, fn) {
            bool = (typeof bool == "boolean") ? bool : true;
            handle = handle || el;
            if (Jelo.Valid.isElement(el) && Jelo.Valid.isElement(handle)) {
                handle.jeloDragTarget = el;
                Jelo.on(handle, "mousedown", mouseDown);
                Jelo.css(handle, "cursor", "move");
                el.jeloDraggy = bool;
                el.jeloDragHandle = handle;
                if (typeof fn == "function") {
                    el.jeloDragOnMouseUp = fn;
                }
            }
        },
        /**
         * Starts or stops listening for drop events on a given element.
         *
         * @param {HTMLElement} element The item to affect.
         * @param {Boolean} [bool=true] True to start listening for drop events, false
         * to stop.
         * @param {HTMLElement} [handle=element] The area that, when dropped
         * upon, will register the event. Defaults to the whole element itself.
         * @param {Function} [fn] Method to invoke when something is dropped on
         * this element. The execution context ("this") will be the drop zone
         * element itself, and the arguments passed will be the dropped element
         * and the drop zone's handle, respectively.
         */
        setDroppy      : function(el, bool, handle, fn) {
            bool = (typeof bool == "boolean") ? bool : true;
            handle = handle || el;
            if (Jelo.Valid.isElement(el) && Jelo.Valid.isElement(handle)) {
                handle.jeloDropTarget = el;
                el.jeloDroppy = bool;
                el.jeloDropHandle = handle;
                if (typeof fn == "function") {
                    el.jeloOnDrop = fn;
                }
                _.dropTargets.push(el);
            }
        },
        /**
         * Turns on drag-drop support.
         */
        on             : function() {
            active = true;
        },
        /**
         * Turns on drag-drop support. Elements retain any registered drag
         * handlers while drag-drop support is off, but no such events are
         * triggered.
         */
        off            : function() {
            active = false;
        },
        /**
         * Sets how far an element must be dragged before it "counts" as a drag.
         *
         * @param {Number} n How many pixels an element must be dragged before
         * it "counts" as a drag.
         */
        setMinimumDrag : function(n) {
            n = pi(n);
            if (!isNaN(n)) {
                _.minOffset = n;
            }
        }
    };
}();
